package chatNI;

import java.io.File;
import java.io.IOException;

import java.net.*;
import java.util.Observable;
import java.util.Observer;
import java.util.*;
import chatController.*;
import chatModel.*;
import chatModel.ChatModel.Val;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

import signals.*;

/**
 *
 * @author deepali and vansh
 */
public class ChatNI implements Observer {

    /**
     * @param DEST_PORT socket number of the remote user for this application
     * for UDP communication
     * @param LISTEN_PORT the socket number that we use to listen on the
     * application for the local user for UDP
     * @param DEST_FILE_PORT socket number of the remote user for this
     * application for TCP communication
     * @param LISTEN_FILE_PORT the socket number that we use to listen on the
     * application for the local user for TCP
     * @param BROADCAST the broadcast address which is equal to 255.255.255.255
     * @param rcvThread the thread that is use to listen/receive
     * @param bool to know if file transfer request is accepted or not
     * @param localIpAdress the local host IP for the network interface used
     * @param broadcastString broadcast address into string
     * @param localIPAdressString localIpAdress into String type
     * @param cm the chat model used for this instance of the chat system
     * @param cc the chat controller used for this instance of the chat
     * controller
     */
    protected static final int DEST_PORT = 4444;
    protected static final int LISTEN_PORT = 4444;
    protected static final int DEST_FILE_PORT = 4444;
    protected static final int LISTEN_FILE_PORT = 4444;
    private ThreadReceiveMsg rcvThread;

    private boolean bool;
    private ChatModel cm;
    private ChatController cc;
    private InetAddress localIpAdress;
    private InetAddress broadcast;
    private String broadcastString;
    private String localIPAdressString;

    public String getBroadcastString() {
        return broadcastString;
    }

    public void setBroadcastString(String broadcastString) {
        this.broadcastString = broadcastString;
    }

    public String getLocalIPAdressString() {
        return localIPAdressString;
    }

    public void setLocalIPAdressString(String localIPAdressString) {
        this.localIPAdressString = localIPAdressString;
    }

    /**
     * Method to set chat model
     *
     * @param cm
     */
    public ChatNI(ChatModel cm) {
        this.cm = cm;

        System.out.println("STarting the listening thread here  ");
    }

    /**
     * This method allows us to start the listening thread when the user gets
     * connected
     */
    public void startThread() {
        rcvThread = new ThreadReceiveMsg(this, LISTEN_PORT);
        rcvThread.start();

    }

    /**
     * This method allows us to set current chat controller for this instance of
     * the chat system
     *
     * @param current chatController
     */
    public void setController(ChatController chatController) {
        this.cc = chatController;
    }

    /**
     * This method is used to get the localIpadress of the local host for the
     * network interface String form of the IPAdress is also stored as
     * localIpAdressString
     *
     * @param inter String of the network interface of local host being used
     * @throws UnknownHostException
     */
    public void getIpOfInterfac(String inter) throws UnknownHostException, SocketException {

        try {
            for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
                NetworkInterface intf = en.nextElement();

                if (intf.getName().equals(inter)) {

                    for (InterfaceAddress intAddress : intf.getInterfaceAddresses()) {
                        {
                            if (intAddress.getAddress().getAddress().length == 4) {
                                localIpAdress = intAddress.getAddress();
                                localIPAdressString = localIpAdress.getHostAddress();
                                broadcast = intAddress.getBroadcast();
                                broadcastString = broadcast.getHostAddress();
                                System.out.println("Broadcast:" + broadcastString);
                                // System.out.println(intAddress.getBroadcast());
                            }
                        }
                    }
                }
            }
        } catch (SocketException e) {
            System.out.println(" (error retrieving network interface list)");
        }

    }

    /**
     * This method allows to Broadcast hello to all users connected to system
     * when the local user connects. It sends the name and Ip address.
     *
     * @param hostname
     * @throws SignalTooBigException
     */
    public void sendHello(String hostname) throws SignalTooBigException {
        // TODO Auto-generated method stub
        try {
            Hello hello = new Hello(hostname);
            ThreadSendMsg msgThread = new ThreadSendMsg(Hello.toByteArray(hello), InetAddress.getByName(broadcastString), DEST_PORT, true);
            msgThread.start();
            System.out.println("Hello sent to everyone on the broadcast address");

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * This method sends Hello OK on receiving a hello only to that user from
     * which we receive that Hello. It sends username and IP address
     *
     * @param localusername
     * @param connectorhostname
     * @throws UnknownHostException
     */
    private void sendHelloOK(String localusername, String connectorhostname) throws UnknownHostException {
        HelloOK hellook = new HelloOK(localusername);
        ThreadSendMsg msgThread;
        try {
            msgThread = new ThreadSendMsg(HelloOK.toByteArray(hellook), InetAddress.getByName(connectorhostname), DEST_PORT, false);
            msgThread.start();
        } catch (SignalTooBigException | IOException ex) {
            Logger.getLogger(ChatNI.class.getName()).log(Level.SEVERE, null, ex);
        }

        System.out.println("HelloOK sent from" + localusername + "to" + InetAddress.getByName(connectorhostname)); //To change body of generated methods, choose Tools | Templates.
    }

    /**
     * sendbye method sends a GOODBYE to all connected user (broadcast) when the
     * local user disconnects. It performs performbyesent method of chat
     * controller which disconnects the local user.
     *
     * @param hostname
     * @throws SignalTooBigException
     * @throws IOException
     */
    public void sendBye(String hostname) throws SignalTooBigException, IOException {
        // TODO Auto-generated method stub

        try {

            Goodbye bye = new Goodbye(hostname);
            ThreadSendMsg threadSendMsg;
            threadSendMsg = new ThreadSendMsg(Goodbye.toByteArray(bye), InetAddress.getByName(broadcastString), DEST_PORT, true);
            threadSendMsg.start();
            cc.performByeSent();
        } catch (UnknownHostException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * Receive method allows us to analyze the received packet and by that, we
     * will be able to determine if the packet is an instance of a hello,helloOK
     * bye, message or file transfer or its acceptance/rejection . This method
     * is called by the listening thread when it receives packet The
     * corresponding methods from chat controller are called in each case.
     * Receive hello updates user list and sends hello OK
     *
     * Receive Hello OK updates user list Receive Goodbye removes user from list
     *
     * Receive text message adds message to conversation
     *
     * Receive FileProposal asks user if it wants to accept or reject and send
     * corresponding signal If filetransferAccepted is received, file is sent
     *
     * @param myPacket
     * @throws IOException
     * @throws ClassNotFoundException
     * @throws SignalTooBigException
     */
    public void Receive(DatagramPacket myPacket) throws IOException, ClassNotFoundException, SignalTooBigException {
        // TODO Auto-generated method stub

        byte[] data = myPacket.getData();
        Signal msgRcvd = Signal.fromByteArray(data);

        if (msgRcvd instanceof TextMessage) {

            TextMessage textmsg = (TextMessage) msgRcvd;

            System.out.println("Message  " + textmsg.getMessage() + " from " + textmsg.getFrom());

            cc.performMsgRcvd(cm.getHashmapUsers().get(textmsg.getFrom()), textmsg.getMessage());
        }

        if (msgRcvd instanceof Hello) {
            Hello hello = (Hello) msgRcvd;
            System.out.println("I received a message from " + Hello.fromByteArray(data).getClass());
            System.out.println("It came from" + hello.getUsername());

            System.out.println(myPacket.getAddress());
            System.out.println(InetAddress.getByName("localhost"));
            String s = cm.getLocalUser().getUsername();
            
            if (!hello.getUsername().equals(s)) {
                System.out.println("check" + s + " " + hello.getUsername());
                cc.performHelloRcvd(hello.getUsername(), myPacket.getAddress().getHostName());
                System.out.println("I m sending a hellook on receiving a hello");
                this.sendHelloOK((cm.getLocalUser().getUsername()), (myPacket.getAddress().getHostName()));
            }
      
        } else if (msgRcvd instanceof HelloOK) {
            HelloOK hellook = (HelloOK) msgRcvd;
            System.out.println("I received a message from " + HelloOK.fromByteArray(data).getClass());
            System.out.println("It came from" + hellook.getUsername());
            System.out.println("I have to update the user in the userlist after receiving a HelloOK");
            cc.performHelloOKRcvd(hellook.getUsername(), myPacket.getAddress().getHostName());
        } else if (msgRcvd instanceof Goodbye) {
            Goodbye goodbye = (Goodbye) msgRcvd;
            System.out.println("Username that gave goodbye was :" + goodbye.getUsername());
            cc.performByeRcvd(cm.getHashmapUsers().get(goodbye.getUsername()));
        } else if (msgRcvd instanceof FileProposal) {
            FileProposal fileproposal = (FileProposal) msgRcvd;
            System.out.println("received fileproposal from " + fileproposal.getFrom());
            cc.performNotifybeforeFileReception(fileproposal.getFrom());

            cc.performFileRequestRcvd(fileproposal.getFrom(), fileproposal.getFileName());

            if (bool) {
                cc.releasebuttonpressed();
                FileTransferAccepted fnaa = new FileTransferAccepted(fileproposal.getFileName(), fileproposal.getFrom());
                ThreadSendMsg threadsend = new ThreadSendMsg(FileTransferAccepted.toByteArray(fnaa), myPacket.getAddress(), DEST_PORT, false);
                threadsend.start();
                ThreadReceiveFile threadreceive = new ThreadReceiveFile(fileproposal.getFrom(), this, LISTEN_FILE_PORT, fileproposal.getFileName());
                threadreceive.start();
            } else {
                System.out.println("sending filetransferNOtAccepted to :" + fileproposal.getFrom());
                FileTransferNotAccepted fnaa = new FileTransferNotAccepted(fileproposal.getFileName(), fileproposal.getFrom());
                ThreadSendMsg threadsend = new ThreadSendMsg(FileTransferNotAccepted.toByteArray(fnaa), myPacket.getAddress(), DEST_PORT, false);
                threadsend.start();
            }
        } else if (msgRcvd instanceof FileTransferAccepted) {

            FileTransferAccepted fta = (FileTransferAccepted) msgRcvd;
            System.out.println("received filetransferAccept and the host name is :");//
            System.out.println(fta.getRemoteUsername());
            System.out.println("And the file name is :");
            System.out.println(fta.getFileName());
            cc.performFileAnswerRcvd(myPacket.getAddress().getHostName(), myPacket.getAddress());

        } else if (msgRcvd instanceof FileTransferNotAccepted) {
            FileTransferNotAccepted fta = (FileTransferNotAccepted) msgRcvd;
            System.out.println("received filetransferNAccpted and the host name is :");//
            System.out.println(myPacket.getAddress());
            System.out.println("And the file name is :");
            System.out.println(fta.getFileName());
        }

    }

    /**
     * sendMsg allows us to send a text message to a specific user. we start a
     * sending thread and then we send the message to user using his ip address
     *
     * @param DestUser the name of the user we want to send him the message
     * @param msgToSend this the message that we want to send to the remote user
     */
    public void sendMsg(String DestUser, String msgToSend) throws SignalTooBigException {
        // TODO Auto-generated method stub
        try {
            ArrayList a1 = new ArrayList();
            a1.add(DestUser);
            String hostname = cm.getHashmapUsers().get(DestUser).getHostname();
            System.out.println("Chat message sending");
            System.out.println("the message sent is " + msgToSend + " and destination is " + hostname + " " + DestUser);
            TextMessage msgtosend = new TextMessage(msgToSend, cm.getLocalUser().getUsername() + "@" + localIPAdressString, a1);
            ThreadSendMsg msgThread = new ThreadSendMsg(TextMessage.toByteArray(msgtosend), InetAddress.getByName(hostname), DEST_PORT, false);
            msgThread.start();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

    /**
     *
     * update is a method of the observer pattern that allows us to send a hello
     * or a bye message when the user changes his connection state
     *
     * @param arg0 is the observed class
     * @param arg is the object
     */
    public void update(Observable arg0, Object arg) {
        // TODO Auto-generated method stub
        if (arg == Val.CONNEXIONSTATE) {
            if (arg instanceof User) {
                if ((User) arg == cm.getLocalUser()) {
                    try {
                        sendHello(broadcastString);
                    } catch (SignalTooBigException ex) {
                        Logger.getLogger(ChatNI.class.getName()).log(Level.SEVERE, null, ex);
                    }
                } else {
                    try {

                        sendHello(((User) arg).getUsername());
                    } catch (SignalTooBigException ex) {
                        Logger.getLogger(ChatNI.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
        }

    }

    /**
     * sendFile send the file to required user through tcp connection.
     * threadSendFile is started
     *
     * @param filepath of file to be sent
     * @param ip ip of destination user
     * @param username name of destination user
     * @throws UnknownHostException
     */
    public void sendFile(String filepath, InetAddress ip, String username) throws UnknownHostException {
        System.out.println("Enterred Send File ChatNI");
        File fich = new File(filepath);
        System.out.println(username);
        System.out.println(cm.getHashmapUsers().get(username));

        ThreadSendFile sendFileThread = new ThreadSendFile(username, this, ip, DEST_FILE_PORT, fich);
        sendFileThread.start();
    }

    /**
     * sendFileRequest method allows us to send a notify file message to notify
     * a remote user that we want to send him/her a file. We send a fileProposal
     * signal
     *
     * @param filename this is a String and it consists of the name the file
     * that we want to send
     * @param username this is the remote user name of the person to whom we
     * want to send the file
     * @param size this is the size of file
     */
    public void sendFileRequest(String username, String filename, long size) throws SignalTooBigException, IOException {
        ArrayList a1 = new ArrayList();
        a1.add(username);
        String hostname = cm.getHashmapUsers().get(username).getHostname();
        FileProposal fileproposal = new FileProposal(filename, size, cm.getLocalUser().getUsername(), a1);
        ThreadSendMsg threadsend = new ThreadSendMsg(FileProposal.toByteArray(fileproposal), InetAddress.getByName(hostname), DEST_PORT, false);
        threadsend.start();
    }

    /**
     * It sets the boolean bool
     *
     * @param accept
     */
    public void setBoolForfileTransfer(boolean accept) {
        bool = accept;
    }

    /**
     * sendFileAnswer method allows us to send a notify file message to notify a
     * remote user that we accept the file the they want to send us
     *
     * @param filename this is a String and it consists of the name the file
     * that we want to send
     * @param user this is the remote user to whom we want to send the file
     * @param bool it tells if file proposal was accepted or rejected
     */
    public void sendFileAnswer(User user, String fileName, boolean accepted) {
        this.bool = accepted;
    }

    /**
     * fileSent method allows us to know when the file has been sent
     * successfully or not
     *
     * @param username this is the remote user name of the person to whom we
     * want to send the file
     */
    public void fileSent(String username) {
        cc.performFileSent(username);
    }

    /**
     * fileReceived method allows us to know when the file has been received
     * successfully or not
     *
     * @param username this is the remote user name of the person to whom we
     * want to send the file
     */
    public void fileReceived(String username) {
        cc.performFileReceived(username);
    }

}
